use std::{future::{ready, Ready}, mem::MaybeUninit, sync::Arc, time::Duration};

use actix_web::{FromRequest, HttpRequest};
use dashmap::DashMap;
use tokio::time::sleep;

use crate::{
    error::{new_err_static, AppErr},
    store::user::User,
    utils::current_timestamp,
};

#[derive(Debug, Clone)]
pub struct Session {
    pub active_timestamp: u64,
    pub user: Arc<User>,
}

const TOKEN_HEAD: &'static str = "token";

pub const SESSION_DURATION: u64 = 4 * 60 * 60;

static mut SESSION_MAP: MaybeUninit<DashMap<String, Session>> = MaybeUninit::uninit();

pub fn init() {
    unsafe {
        SESSION_MAP.write(DashMap::new());
    }
    tokio::spawn(active_remove_task());
}

#[inline(always)]
fn get_map() -> &'static DashMap<String, Session> {
    unsafe { SESSION_MAP.assume_init_ref() }
}

async fn active_remove_task() {
    loop {
        sleep(Duration::from_secs(SESSION_DURATION)).await;
        let cur = current_timestamp();
        let m = get_map();
        m.retain(|_, v| (v.active_timestamp + SESSION_DURATION) > cur);
        m.shrink_to_fit();
    }
}

pub fn save_session(token: String, user: Arc<User>) {
    let session = Session {
        active_timestamp: current_timestamp(),
        user,
    };
    get_map().insert(token, session);
}

fn get_session(token: &str) -> Result<Session, AppErr> {
    let session = get_map()
        .get(token)
        .ok_or(new_err_static("没有登陆或者操作超时"))?;

    if (session.value().active_timestamp + SESSION_DURATION) < current_timestamp() {
        return Err(new_err_static("操作超时"));
    }

    Ok(session.clone())
}


fn parse_session(req: &HttpRequest) -> Result<Session, AppErr> {
    let token = req.headers().get(TOKEN_HEAD)
        .ok_or(new_err_static("not token"))?
        .to_str()
        .map_err(|_| new_err_static("token parse fail"))?;

    get_session(token)
}

impl FromRequest for Session {

    type Error = AppErr;
    type Future = Ready<Result<Session, AppErr>>;

    fn from_request(req: &HttpRequest, _payload: &mut actix_web::dev::Payload) -> Self::Future {
        ready( parse_session(req) )
    }
}









